/*********************************************************************************
 *      Copyright:  (C) 2022 xiezongyi<1764417007@qq.com>
 *                  All rights reserved.
 *
 *       Filename:  client_no_thread.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(07/22/22)
 *         Author:  xiezongyi <1764417007@qq.com>
 *      ChangeLog:  1, Release initial version on "07/22/22 12:30:28"
 *                 
 ********************************************************************************/
#include <time.h>
#include <stdio.h>
#include <errno.h>
#include <libgen.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sqlite3.h>
#include <stdlib.h>
#include <getopt.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/tcp.h>

#include <openssl/ssl.h>
#include <openssl/err.h>

#include <event.h>
#include <event2/util.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/bufferevent_ssl.h>

#include "tlv_client.h"
#include "client.h"
#include "database_client.h"
#include "temperature.h"

#define LEN		128
#define SNO		"XIEZONGYI001"

int				  g_stop = 0;
struct event_base *base = NULL;

int main (int argc, char **argv)
{
	int					ch;
	int					port = 0;
	int					clifd = 0;
	int					daemon_run = 0;
	char				s_buf[128];
	char				*servip = NULL;
	char				*db_name = "temperature.db";
	sqlite3				*db;
	sample_send_t		s_send;
	struct timeval		timeout;
	struct sockaddr_in	servaddr;
	struct bufferevent	*bev = NULL;

	struct option			opts[] = 
	{
		{"ipaddr", required_argument, NULL, 'i'},
		{"port", required_argument, NULL, 'p'},
		{"time", required_argument, NULL, 't'},
		{"daemon", no_argument, NULL, 'd'},
		{"help", no_argument, NULL, 'h'},
		{NULL, 0, NULL, 0}
	};

	char *progname = basename(argv[0]);

	while((ch = getopt_long(argc, argv, "i:p:t:dh", opts, NULL)) != -1)
	{
		switch(ch)
		{
			case 'i':
				servip = optarg;
				break;
			case 'p':
				port = atoi(optarg);
				break;
			case 't':
				s_send.second = atoi(optarg);
				break;
			case 'd':
				daemon_run = 1;
				break;
			case 'h':
				print_usage(progname);
				return 0;
		}
	}

	if(!servip || !port || !(s_send.second))
	{
		print_usage(progname);
		return 0;
	}

	if(daemon_run)
	{
		daemon(1, 0);
	}

	signal(SIGTERM, signal_handler);
	signal(SIGPIPE, signal_handler);

	//打开或创建数据库、建表
	if(database_init(&db, db_name) < 0)
	{
		printf("database_init() failure: %s\n", strerror(errno));
		return -1;
	}
	else
	{
		s_send.db = db;
	}

	while(!g_stop)
	{
		//初始化ssl环境
        SSL_library_init();
        SSL_load_error_strings();
        SSL_CTX *ctx = SSL_CTX_new(SSLv23_client_method());
        if(!ctx)
        {
            printf("SSL_CTX_new() failure: %s\n", strerror(errno));
            exit(1);
        }

		//填充结构体
		if(get_sample_data(&(s_send.data), s_buf) < 0)	
		{
			printf("Filled the struct data failure: %s\n", strerror(errno));
		}

		base = event_base_new();
		if(!base)
		{
			printf("Event_base_new() failure: %s\n", strerror(errno));
			return -1;
		}

		clifd = socket(AF_INET, SOCK_STREAM, 0);
		if(clifd < 0)
		{
			printf("Socket() failure: %s\n", strerror(errno));
			return -2;
		}
		s_send.fd = clifd;

		SSL *ssl = SSL_new(ctx);
		bev = bufferevent_openssl_socket_new(base, -1, ssl, BUFFEREVENT_SSL_CONNECTING
, BEV_OPT_CLOSE_ON_FREE);
		if(!bev)
		{
			printf("bufferevent_openssl_socket_new() failure: %s\n", strerror(errno));
			return -2;
		}

		memset(&servaddr, 0, sizeof(servaddr));
		servaddr.sin_family = AF_INET;
		servaddr.sin_port = htons(port);
		inet_aton(servip, &servaddr.sin_addr);

		if( bufferevent_socket_connect(bev, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
		{
			printf("Connect server[%s:%d] failure: %s\n", servip, port, strerror(errno));
			return -2;
		}

		evutil_make_socket_nonblocking(clifd);
		//监听写事件
		s_send.bev = bev;
		struct event *ev = event_new(base, clifd, EV_WRITE, get_msg_cb, &s_send);
		if( event_add(ev, NULL) < 0 ) 
		{
			printf("Add write event failure: %s\n", strerror(errno));
			return -3;
		}

		//设置超时事件
		struct event *ev_time = event_new(base, -1, 0, timeout_cb, &s_send);
		s_send.event = ev_time;
		timeout.tv_sec = s_send.second;
		timeout.tv_usec = 0;
		if(event_add(ev_time, &timeout) < 0)
		{
			printf("Add timeout event failure: %s\n", strerror(errno));			
			return -4;
		}
	
		bufferevent_setcb(bev, NULL, write_cb, event_cb, &s_send);
		bufferevent_enable(bev, EV_WRITE | EV_READ);

		//阻塞，循环执行
		event_base_dispatch(base);

		bufferevent_free(bev);
		event_base_free(base);
		SSL_CTX_free(ctx);

	}

	return 0;
} 

void get_msg_cb(evutil_socket_t socket, short what, void *arg)
{
 	struct tm   	*tm;
    time_t      	cur_time = 0;
    sample_send_t   *ptr = (sample_send_t *)arg;

    time(&cur_time);
    tm = localtime(&cur_time);
	packtlv_msg(&(ptr->tlv), ptr->data.ID, ptr->data.temp, tm);
    if( bufferevent_write(ptr->bev, ptr->tlv.buf, sizeof(ptr->tlv.buf)) < 0)
    {
        printf("Send data to server failure: %s\n", strerror(errno));
    }
}

void write_cb(struct bufferevent *bev, void *arg)
{
	sample_send_t *p = (sample_send_t *)arg;

	dump_buf(p->tlv.buf, p->tlv.len);
}

void event_cb(struct bufferevent *bev, short events, void *arg)
{
	if(events & BEV_EVENT_CONNECTED)
	{
		//连接成功时走到这里，并且要客户端第一次触发“读事件”后连接才真正建立起来
		printf("Connect successfully!\n");
	}
	else if(events & (BEV_EVENT_ERROR | BEV_EVENT_EOF))
	{	
		if(events & BEV_EVENT_ERROR)
		{
			printf("err = %s\n", strerror(EVUTIL_SOCKET_ERROR()));
		}
		printf("Connect closing\n");
		bufferevent_free(bev);
		event_base_loopexit(base, NULL);
	}
}

void signal_handler(int signum)
{
	if(SIGTERM == signum)
	{
		printf("SIGTERM signal detected!\n");
		g_stop = 1;
	}
	else if(SIGPIPE == signum)
	{
		printf("SIGPIPE signal detected\n");
	}
}

void print_usage(char *progname)
{
	printf("%s usage:\n", progname);
	printf("-i(--ipaddr):specify  server IP address\n");
	printf("-p(--port):specify server port\n");
	printf("-t(--time):specify second time\n");
	printf("-h(--help):print this help information\n");

	return;
}

int get_sample_data(struct sample_data *sample, char *s_buf)
{
	char		datatime[64];
    time_t      tmp;
    struct tm   *timp;

    time(&tmp);
    timp = localtime(&tmp);

	if(get_temperature(&(sample->temp)) < 0)
	{
		printf("Get the ds18b20 temperature failure:%s\n", strerror(errno));
		return -1;
	}

	strncpy(sample->ID, SNO, sizeof(sample->ID));

    memset(s_buf, 0, sizeof(*s_buf));
    memset(datatime, 0, sizeof(datatime));
    strftime(datatime, 128, "%Y-%m-%d %H:%M:%S", timp);
    snprintf(s_buf, 128, "%s/%s/%.2f°C", sample->ID, datatime, sample->temp);

	return 0;
}

void timeout_cb(evutil_socket_t fd, short what, void *arg)
{
	int				sample_flag = 0;
	char			data[64];
	char			s_buf[64];
	struct timeval 	tv;
	struct tm   	*tm;
    time_t      	cur_time = 0;

    time(&cur_time);
    tm = localtime(&cur_time);

	sample_send_t	*ptr = (sample_send_t *)arg;

	tv.tv_sec = ptr->second;
	tv.tv_usec = 0;
	if(event_add((ptr->event), &tv) < 0)
	{
		printf("Add timeout event second failure: %s\n", strerror(errno));
		exit(1);
	}

	//采样并填充结构体
	if( get_sample_data(&(ptr->data), s_buf) < 0)
	{
		printf("Filled the struct data failure: %s\n", strerror(errno));
	}

	//打包数据
	if( packtlv_msg(&(ptr->tlv), ptr->data.ID, ptr->data.temp, tm) < 0 )
	{
		printf("packtlv_msg() failure: %s\n", strerror(errno));
	}
	else
	{
		sample_flag = 1;
	}

	if(get_tcp_info(ptr->fd) == 0)
	{
		if(sample_flag)
		{
			if(bufferevent_write(ptr->bev, ptr->tlv.buf, strlen(ptr->tlv.buf)) < 0)
			{
				printf("Send data to server failure: %s\n", strerror(errno));
			}
			dump_buf(ptr->tlv.buf, ptr->tlv.len);
		}

		if( database_pop_first(ptr->db, data) == 0 ) 
		{
			if( bufferevent_write(ptr->bev, data, strlen(data)) < 0)
			{
				printf("Send data from database to server failure: %s\n", strerror(errno));
			}
			else
			{
				if(database_delete_first(ptr->db) < 0)
				{
					printf("Delect data from database OK!\n");
				}
				else
				{
					printf("Delect data from database failure: %s\n", strerror(errno));
				}
			}
		}
		else
		{
			printf("The database is NULL!\n");
		}
	}
	else
	{
		if(sample_flag)
		{
			if( database_insert(ptr->db, s_buf) < 0 ) 
			{
				printf("Insert data to database failure: %s\n", strerror(errno));
			}
			else
			{
				printf("Insert data to database OK!\n");
			}
		}
	}
}

int get_tcp_info(int sockfd)
{
	struct tcp_info	info;

	socklen_t	len = sizeof(info);

	if( 0 == getsockopt(sockfd, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len) )
	{
		if(info.tcpi_state != TCP_ESTABLISHED)
		{
			printf("disconnect!\n");
			return -1;
		}
	}

	return 0;
}
